
import { ExecutiveContext, State,Executive, Autherizable, ActivityStatus, ActionType } from "../defination"
import { ActivityRecord, ByteArray, ExecutionRecord, ExecutiveRecord, StatusRecord } from "../entity"
import {  ACTIVITY, ACTIVITY_STATUS, DEALER, EXECUTION_RECORD, INPUT, NEXT, OUTPUT, REQUEST, RESULT, RETURN_VALUE, STATE_RECORD, VARIABLE } from "../shared-symbols"
import { unreadonly } from "../readonly"
import { BaseAction } from "./action"
import type { BeginAction } from "./beginAction"
import type { EnterAction } from "./enterAction"

import { SuspendAction } from "./suspendAction"
import { DoneAction } from "./doneAction"
import { merge } from "../copy"
import { invokeExecutiveAction } from "../executive"
import { DealResult } from "../deal-result"

export class DealAction
<
  TActivity extends Executive<TActivity,TState,TRecord,TContext>,
  TState extends State=State,
  TRecord extends ExecutiveRecord=ExecutiveRecord,
  TContext extends ExecutiveContext=ExecutiveContext
> extends BaseAction<TActivity,TState,TRecord,TContext>
{


  constructor(act:BeginAction<TActivity,TState,TRecord,TContext> | EnterAction<TActivity,TState,TRecord,TContext>){
    super(act)
  }

 
  public get result():DealResult|undefined{
    return this[RESULT]
  }
  async invoke(context:TContext):Promise<BaseAction<TActivity,TState,TRecord,TContext>|null|undefined>{
    const activity = this[ACTIVITY]
    const currentStatus = activity[ACTIVITY_STATUS]
    if (currentStatus !== ActivityStatus.ENTERED && currentStatus !== ActivityStatus.SUSPENDED) {
      throw new Error('only can invoke enter action when the activity is entered/suspended')
    }
    
    const activityRecord = this[STATE_RECORD] as unknown as ActivityRecord

    const input = context[REQUEST].input
    const inputBytes = input==undefined?undefined:context.serializer.serialize(input)

    const variable = unreadonly(activity.variable)
    const lastOutput = unreadonly(activity.output)
    let variableBytes:ByteArray = activityRecord.variable
    let outputBytes:ByteArray|undefined = activityRecord.output
    if (lastOutput!==undefined){
      if (typeof lastOutput === 'object'){
        merge(lastOutput|| {},variable)
      }else {
        (variable as {['$value']:unknown}).$value = lastOutput
      }
      variableBytes = context.serializer.serialize(variable)
      outputBytes = undefined
    }

    const at = context.calendar.now()
    const dealerId = context[DEALER].id
    const nextStatus = ActivityStatus.DEALING
    const statusBytes = context.enumConverter.toByteArray<ByteArray>(nextStatus,ActivityStatus,true)!
    
    activity[ACTIVITY_STATUS] = nextStatus
    activity[VARIABLE] = variable
    activity[OUTPUT] = undefined
    activity[INPUT] = input

    activityRecord.activityStatus = statusBytes
    activityRecord.dealTime = at
    activityRecord.input = inputBytes
    activityRecord.variable = variableBytes
    activityRecord.output = outputBytes
    activityRecord.modifierId = dealerId
    activityRecord.updateTime = at

    const executionRecord = this[EXECUTION_RECORD] as ExecutionRecord
    executionRecord.activityStatus = statusBytes
    executionRecord.input = inputBytes
    executionRecord.variable = variableBytes
    executionRecord.output = outputBytes
    executionRecord.updateTime = at

    await context.repository.dealingActivity(activityRecord,this[EXECUTION_RECORD]!,{
      entered:context.enumConverter.toByteArray<ByteArray>(ActivityStatus.ENTERED,ActivityStatus,true)!,
      suspended:context.enumConverter.toByteArray<ByteArray>(ActivityStatus.SUSPENDED,ActivityStatus,true)!
    },context[REQUEST].requestId)
    const returnValue = await invokeExecutiveAction<TActivity,TState,TRecord,TContext>(this.activity,ActionType.DEAL,context)
    this[RESULT] = DealResult.make(returnValue)
    
    
    return this.next
  }

  get next():
  | SuspendAction<TActivity,TState,TRecord,TContext>
  | DoneAction<TActivity,TState,TRecord,TContext>
  | null
  | undefined
  {
    if (this[RESULT]===undefined)
      return undefined
    
    if (this[NEXT]===undefined)  {
      const result = this[RESULT]
      if (result.dealStatus === 'deal-success'){
        return this[NEXT] = new DoneAction(this)
      }else if (result.dealStatus === 'deal-failed'){
        return this[NEXT] = new SuspendAction(this)
      }else if (result.dealStatus === 'deal-suspended'){
        return this[NEXT] = new SuspendAction(this)
      }else {
        return this[NEXT] = null
      }
    }
    return this[NEXT] as DoneAction<TActivity,TState,TRecord,TContext> | SuspendAction<TActivity,TState,TRecord,TContext> | null | undefined
  }
}